﻿using System;
using System.Runtime.CompilerServices;

namespace ManagedTFRecordLibrary
{
    internal readonly struct Crc32
    {
        public const uint DefaultPolynomial = 0xedb88320u;
        public const uint CastagnoliPolynomial = 0x82f63b78;

        private readonly uint[] _table;
        private readonly uint _value;

        private static uint[] s_defaultTable;
        private static uint[] s_castagnoliTable;

        public uint Value => _value;

        public Crc32(uint polynomial)
        {
            _table = InitializeTable(polynomial);
            _value = 0;
        }

        public Crc32(uint polynomial, uint initialValue)
        {
            _table = InitializeTable(polynomial);
            _value = initialValue;
        }
        public Crc32(uint[] table, uint initialValue)
        {
            if (table == null)
            {
                throw new ArgumentNullException(nameof(table));
            }
            if (table.Length != 256)
            {
                throw new ArgumentException(nameof(table.Length));
            }
            _table = table;
            _value = initialValue;
        }

        private static UInt32[] InitializeTable(UInt32 polynomial)
        {
            if (polynomial == DefaultPolynomial && s_defaultTable != null)
            {
                return s_defaultTable;
            }
            if (polynomial == CastagnoliPolynomial && s_castagnoliTable != null)
            {
                return s_castagnoliTable;
            }

            var createTable = new UInt32[256];
            for (var i = 0; i < 256; i++)
            {
                var entry = (UInt32)i;
                for (var j = 0; j < 8; j++)
                {
                    if ((entry & 1) == 1)
                    {
                        entry = (entry >> 1) ^ polynomial;
                    }
                    else
                    {
                        entry = entry >> 1;
                    }
                }
                createTable[i] = entry;
            }

            if (polynomial == DefaultPolynomial)
            {
                s_defaultTable = createTable;
            }
            if (polynomial == CastagnoliPolynomial)
            {
                s_castagnoliTable = createTable;
            }
            return createTable;
        }

        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        private uint[] PrepareTable()
        {
            return _table == null ? InitializeTable(DefaultPolynomial) : _table;
        }

        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public Crc32 Append(byte[] buffer, int offset, int count)
        {
            return AppendCore(new Span<byte>(buffer, offset, count));
        }

        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public Crc32 Append(Span<byte> data)
        {
            return AppendCore(data);
        }

        private Crc32 AppendCore(Span<byte> data)
        {
            uint[] table = PrepareTable();
            uint value = _value ^ 0xffffffffu;
            for (int i = 0; i < data.Length; i++)
            {
                value = (value >> 8) ^ table[data[i] ^ value & 0xff];
            }
            return new Crc32(table, value ^ 0xffffffffu);
        }

        public Crc32 Clear()
        {
            return new Crc32(PrepareTable(), _value);
        }
    }
}
